#include "shader.h"
#include <cstdio>
#include <vector>
#include <memory>
#include <iostream>

namespace vortex
{
	GLenum getShaderTypeEnum(ShaderType type)
    {
        if (type == ShaderType::Vertex)
            return GL_VERTEX_SHADER;
        else if (type == ShaderType::Fragment)
            return GL_FRAGMENT_SHADER;
        else if (type == ShaderType::Compute)
            return GL_COMPUTE_SHADER;
        else if (type == ShaderType::Geometry)
            return GL_GEOMETRY_SHADER;
        else
            return GL_VERTEX_SHADER;
    }

	Shader::Shader(const std::string& filepath, ShaderType type)
	{
		this->type = type;
		// create shader
		std::vector<char> source;
		load_file(filepath, source);

		shader_id = glCreateShader(getShaderTypeEnum(type));
		const char* source_str = source.data();
		int size = static_cast<int>(source.size());
		glShaderSource(shader_id, 1, &source_str, &size);
		glCompileShader(shader_id);
		// TODO: check for compile errors
		int params = -1;
		glGetShaderiv(shader_id, GL_COMPILE_STATUS, &params);
		if (params != GL_TRUE)
		{
			int max_length = 2048;
			int actual_length = 0;
			char log[2048];
			glGetShaderInfoLog(shader_id, max_length, &actual_length, log);
			printf("shader info log for GL index %i:\n%s\n", shader_id, log);
		}
	}

	Shader::~Shader()
	{
		glDeleteShader(shader_id);
	}

	bool Shader::load_file(const std::string& filepath, std::vector<char>& source)
	{
		FILE* fp = fopen(filepath.c_str(), "r");
		if (fp == nullptr)
		{
			printf("cannot open %s\n", filepath.c_str());
			return false;
		}
		fseek(fp, 0, SEEK_END);
		uint64_t filesize = ftell(fp);
		source.resize(filesize);
		fseek(fp, 0, SEEK_SET);
		fread(source.data(), sizeof(char), filesize, fp);
		return true;
	}

	ShaderProgram::ShaderProgram(const std::map<ShaderType, std::string>& sources)
	{
		program_id = glCreateProgram();
		for (auto it : sources)
		{
			std::shared_ptr<Shader> shader = std::make_shared<Shader>(it.second, it.first);
			GLuint shader_id = shader->GetId();
			glAttachShader(program_id, shader_id);
			// keep it from destroyed.
			// shaders.push_back(shader);
		}
		glLinkProgram(program_id);
		GLint success;
    	GLchar infoLog[512];
    	glGetProgramiv(program_id, GL_LINK_STATUS, &success);
    	if (!success) {
        	glGetProgramInfoLog(program_id, 512, NULL, infoLog);
        	std::cerr << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
		}
	}

	ShaderProgram::~ShaderProgram()
	{
		glDeleteProgram(program_id);
	}

	void ShaderProgram::SetTextureUniform(const std::string& name, GLint textureSlot)
	{
		auto location = glGetUniformLocation(program_id, name.c_str());
		glUniform1i(location, textureSlot);
	}

	void ShaderProgram::SetTextureArrayUniform(const std::string& name, GLint* values, int size)
	{
		auto location = glGetUniformLocation(program_id, name.c_str());
		glUniform1iv(location, size, values);
	}

	void ShaderProgram::SetUniformMat4(const std::string& name, const glm::mat4& values)
	{
		auto location = glGetUniformLocation(program_id, name.c_str());
		glUniformMatrix4fv(location, 1, GL_FALSE, &values[0][0]);

	}
}
